package com.kepler.social.app.cases;

import com.kepler.social.app.port.UserPasswordResetPort;
import com.kepler.social.common.constants.BusinessState;
import com.kepler.social.common.constants.Enums;
import com.kepler.social.common.constants.TipsConst;
import com.kepler.social.common.exception.ServiceException;
import com.kepler.social.common.utils.PasswordUtil;
import com.kepler.social.common.utils.StringExtendsUtils;
import com.kepler.social.common.utils.ValidateAssert;
import com.kepler.social.domain.support.sms.SmsBo;
import com.kepler.social.domain.user.account.UserAccountAggregate;
import com.kepler.social.domain.user.account.UserAccountRepository;
import com.kepler.social.domain.user.security.UserSecurityDomainService;
import com.kepler.social.generic.MailHelper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

@Service
@Slf4j
public class UserPasswordResetCase implements UserPasswordResetPort {

    @Resource
    private MailHelper mailHelper;
    /**
     * 注意 用的是一套密码 如果变了 一定要改变
     * kepler-social-web com.kepler.social.web.configuration.SecuritySubUnitConfig#passwordEncoder()
     */
    // TODO 将所有实体相关得业务全部抽入聚合根
    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    private TemplateEngine templateEngine;
    @Resource
    private SmsBo smsBo;
    @Resource
    private UserAccountRepository userAccountRepository;
    @Resource
    private UserSecurityDomainService userSecurityDomainService;


    private final int passwordLength = 10;

    /**
     * TODO 重置密码，按照设计应该是用户安全领域，目前设计是不提示用户，用户点击重置，而是将设置的密码发送到用户手机，如果没有再发送到用户的邮箱。
     *      从用户体验这样很差。应该是给予用户选择，再进行发送。
     *      目前的设计，用户安全无法通过用户ID关联出对应的手机和邮箱，需要用户账号领域进行辅助。所以一个想法，是否安全领域也冗余一份 ？
     *
     * @param userId
     * @return
     * @opt 设置重置策略，一天只能重置一次类似这样。
     */
    @Override
    public Enums.TargetEnum resetPassword(Long userId) {
        UserAccountAggregate aggregate = userAccountRepository.findByUserId(userId);;
        if (aggregate.getRoot().getMobilePhoneNumber() != 0L) {
            this.resetPassword(aggregate.getRoot().getCountryCallingCode(), aggregate.getRoot().getMobilePhoneNumber());
            return Enums.TargetEnum.MOBILE_PHONE;
        } else if (StringUtils.isNoneBlank(aggregate.getRoot().getEmail())) {
            this.resetPassword(aggregate.getRoot().getEmail());
            return Enums.TargetEnum.EMAIL;
        } else {
            log.error("resetPassword userId {} userInfo data error", userId);
            throw new ServiceException(BusinessState.UNKNOWN_ERROR);
        }
    }

    /**
     * 重置密码一定使用户在登录的时候才进行，但是为了功能的扩展，谁知道将来会被用到什么地方，所以，干脆校验写全
     *
     * @param email
     */
    @Override
    public void resetPassword(String email) {
        ValidateAssert.isTrue(StringExtendsUtils.isEmail(email), TipsConst.incorrect_email_format);
        //当前时间戳
        String password = PasswordUtil.randomPassword(passwordLength);
        userSecurityDomainService.resetPasswordByEmail(email, passwordEncoder.encode(password));
        // 发送成功邮件 这个需要计入一个记录 以便错误之后进行重新发送
        // 也可以或者通过event的方式, bus总线。领域模型这种模式也是比较流行。
        Context context = new Context();
        context.setVariable("password", password);
        String mail = templateEngine.process("retrieve_password_mail_template.html", context);
        mailHelper.sendHtmlMail(email, "密码重置", mail);
    }

    /**
     * 因为密码是摘要，无法逆运算，所以需要重新生成一次后更新
     * 未认证的时候不需要对时间进行校验
     * 重置手机密码的极端场景
     * 1、未验证 一部手机，如果重置密码，那么已经跳过验证手机的界面或者验证未成功就点击重置密码
     * 2、未验证 两部手机注册同一个手机号（很多App都支持非设备的手机号），那么一部手机没有点击验证，或者验证中途失败，这本身也是手机未注册
     * 3、废弃 等同于未被注册
     *
     * @param countryCallingCode
     * @param mobilePhoneNumber
     */
    @Override
    public void resetPassword(Integer countryCallingCode, Long mobilePhoneNumber) {
        ValidateAssert.isTrue(StringExtendsUtils.isMobilePhoneNumber(mobilePhoneNumber + ""), TipsConst.incorrect_mobile_phone_format);
        String password = PasswordUtil.randomPassword(passwordLength);
        userSecurityDomainService.resetPasswordByPhone(countryCallingCode, mobilePhoneNumber, passwordEncoder.encode(password));
        smsBo.sendPasswordResetSuccessSms(countryCallingCode, mobilePhoneNumber, password, passwordEncoder.encode(password));
    }


}
